/**
 * @file modbus.c
 * @brief Modbus通信模块实现
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <stdint.h>
#include "modbus.h"

// 串口文件描述符
static int g_serial_fd = -1;

// Modbus从机地址
#define SLAVE_ADDR 0x01

// Modbus功能码
#define FUNC_READ_HOLDING_REGISTERS 0x03

// 寄存器地址
#define REG_TEMPERATURE 0x0001
#define REG_HUMIDITY    0x0002

/**
 * @brief 计算CRC16校验值
 * @param data 数据缓冲区
 * @param len 数据长度
 * @return CRC16校验值
 */
static uint16_t calc_crc16(const uint8_t *data, int len) {
    uint16_t crc = 0xFFFF;
    int i = 0;
    int j = 0;
    
    for (i = 0; i < len; i++) {
        crc ^= data[i];
        for (j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc = crc >> 1;
            }
        }
    }
    
    return crc;
}

/**
 * @brief 配置串口参数
 * @param fd 串口文件描述符
 * @return 成功返回0，失败返回-1
 */
static int serial_config(int fd) {
    struct termios tty;

    // 获取当前串口参数
    if (tcgetattr(fd, &tty) < 0) {
        perror("tcgetattr");
        return -1;
    }

    // 设置波特率
    cfsetospeed(&tty, B4800);
    cfsetispeed(&tty, B4800);

    // 设置数据位、停止位和校验位
    tty.c_cflag &= ~PARENB;  // 无校验
    tty.c_cflag &= ~CSTOPB;  // 1个停止位
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;      // 8位数据位

    // 禁用硬件流控
    tty.c_cflag &= ~CRTSCTS;

    // 设置为原始模式
    tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_oflag &= ~OPOST;

    // 设置超时参数
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 10;    // 1秒超时

    // 应用新的串口参数
    if (tcsetattr(fd, TCSANOW, &tty) < 0) {
        perror("tcsetattr");
        return -1;
    }

    return 0;
}

int modbus_init(const char *port_name) {
    // 打开串口设备
    g_serial_fd = open(port_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (g_serial_fd < 0) {
        perror("open serial port");
        return -1;
    }

    // 配置串口参数
    if (serial_config(g_serial_fd) < 0) {
        close(g_serial_fd);
        g_serial_fd = -1;
        return -1;
    }

    return 0;
}

int modbus_read_th(float *temperature, float *humidity) {
    if (g_serial_fd < 0 || temperature == NULL || humidity == NULL) {
        return -1;
    }

    // 构建Modbus请求帧
    uint8_t request[8] = {
        SLAVE_ADDR,                    // 从机地址
        FUNC_READ_HOLDING_REGISTERS,   // 功能码
        0x00, 0x00,                   // 起始寄存器地址（0x0000）
        0x00, 0x02,                   // 寄存器数量
        0x00, 0x00                    // CRC16校验
    };

    // 计算并添加CRC16校验
    uint16_t crc = calc_crc16(request, 6);
    request[6] = crc & 0xFF;
    request[7] = (crc >> 8) & 0xFF;

    // 打印请求数据帧
    fprintf(stderr, "Request: ");
    int i = 0;
    for (i = 0; i < sizeof(request); i++) {
        fprintf(stderr, "%02X ", request[i]);
    }
    fprintf(stderr, "\n");

    // 发送请求
    if (write(g_serial_fd, request, sizeof(request)) != sizeof(request)) {
        perror("write request");
        return -1;
    }

    // 读取响应
    uint8_t response[9];
    int bytes_read = 0;
    int retry_count = 3;

    while (retry_count-- > 0) {
        bytes_read = read(g_serial_fd, response, sizeof(response));
        if(bytes_read > 0) {
            // 打印响应数据帧
            fprintf(stderr, "Response: ");
            int i = 0;
            for (i = 0; i < bytes_read; i++) {
                fprintf(stderr, "%02X ", response[i]);
            }
            fprintf(stderr, "\n");
        }
        if (bytes_read == sizeof(response)) {
            break;
        }
        usleep(100000);  // 等待100ms
    }

    if (bytes_read != sizeof(response)) {
        fprintf(stderr, "Read response timeout\n");
        return -1;
    }

    // 验证响应
    if (response[0] != SLAVE_ADDR || response[1] != FUNC_READ_HOLDING_REGISTERS || response[2] != 4) {
        fprintf(stderr, "Invalid response\n");
        return -1;
    }

    // 解析温湿度数据
    uint16_t temp_raw = (response[5] << 8) | response[6];  // 温度值在第二个寄存器
    uint16_t humi_raw = (response[3] << 8) | response[4];  // 湿度值在第一个寄存器

    // 温度值使用补码形式，需要进行转换
    int16_t temp_signed = (int16_t)temp_raw;
    *temperature = temp_signed / 10.0f;
    *humidity = humi_raw / 10.0f;

    return 0;
}

void modbus_cleanup(void) {
    if (g_serial_fd >= 0) {
        close(g_serial_fd);
        g_serial_fd = -1;
    }
}